
import {sharedStore} from "../framework/sharedStore.js";
import * as twod from "../canvas/twod.js";
import * as d3 from "d3";
// import * as _ from "../lib/underscore.js";
import * as _ from "underscore";
import {π} from "../util/consts.js";
import {isFirefox, isMobile} from "../util/context.js";
import {$} from "../util/seq.js";

const INTENSITY_SCALE_STEP = 10;            // step size of particle intensity color scale
const PARTICLE_LINE_WIDTH = 1;              // line width of a drawn particle
const PARTICLE_MULTIPLIER = 7;              // particle count scalar (completely arbitrary--this values looks nice)
const PARTICLE_REDUCTION = 0.75;            // reduce particle count to this much of normal for mobile devices
const FRAME_RATE = 40;                      // desired milliseconds per frame

/**
 * @returns {Array} of wind colors and a method, indexFor, that maps wind magnitude to an index on the color scale.
 */
function windIntensityColorScale(step, maxWind) {
    const result = [];
    for (let j = 85; j <= 255; j += step) {
        result.push(`rgb(${j},${j},${j})`);
    }
    result.indexFor = function(m) {  // map wind speed to a style
        return Math.floor(Math.min(m, maxWind) / maxWind * (result.length - 1));
    };
    return result;
}

export function animateParticles(viewboxAgent, globeAgent, primaryLayerAgent, fieldAgent, cancel) {
    const globe = globeAgent.value();
    const layer = primaryLayerAgent.value();
    const field = fieldAgent.value();
    if (!globe || !layer || !field || !sharedStore.get("animation_enabled")) {
        return false;
    }

    twod.clearCanvas(d3.select("#animation").node());  // clear animation artifacts

    const bounds = globe.bounds(viewboxAgent.value());
    const {xMin, yMin, width, height} = bounds;
    // maxIntensity is the velocity at which particle color intensity is maximum
    const colorStyles = windIntensityColorScale(INTENSITY_SCALE_STEP, layer.particles.maxIntensity);
    let particleCount = Math.round(width * PARTICLE_MULTIPLIER);
    if (isMobile()) {
        particleCount = Math.floor(particleCount * PARTICLE_REDUCTION);
    }

    console.log(`particle count: ${particleCount}`);

    const particles = new Float32Array(particleCount * 5);
    const ages = new Int32Array(particleCount);
    const batches = colorStyles.map(() => new Float32Array(particleCount * 4));
    const sizes = new Int32Array(batches.length);
    const scale = globe.projection.scale();

    function randomize(i, field) {
        const x = xMin + Math.random() * width;
        const y = yMin + Math.random() * height;
        field.move(x, y, particles, i);
    }

    function randomizeWell(i, field) {  // This function is hrm, but avoids "pulsing"
        for (let attempts = 0; attempts < 10; attempts++) {
            randomize(i, field);
            if (!isNaN(particles[i+2])) return;
        }
    }

    let maxAge, evolve;
    const g = d3.select("#animation").node().getContext("2d");
    if (layer.particles.waves) {
        maxAge = 40;
        evolve = evolveWaves;
        g.fillStyle = "rgba(0, 0, 0, 0.90)";
    } else {
        maxAge = 100;
        evolve = evolveParticles;
        g.fillStyle = isFirefox() ? "rgba(0, 0, 0, 0.95)" : "rgba(0, 0, 0, 0.97)";  // FF Mac alpha behaves oddly
    }
    g.lineWidth = PARTICLE_LINE_WIDTH;

    for (let i = 0, j = 0; i < particleCount; i += 1, j += 5) {
        ages[i] = _.random(0, maxAge);
        randomizeWell(j, field);
    }

    const easeFactor = new Float32Array(maxAge);
    for (let k = 0; k < easeFactor.length; k++) {
        easeFactor[k] = (Math.sin(-π/2 + k/7)/2 + 1/2);  // fade in/out line intensity
    }

    function evolveWaves() {
        const adj = 600 / scale * Math.pow(Math.log(scale)/Math.log(600), 2.5);  // use shallower exponential speed scale

        for (let s = 0; s < sizes.length; s++) {
            sizes[s] = 0;
        }
        for (let i = 0, j = 0; i < particleCount; i += 1, j += 5) {
            if (++ages[i] >= maxAge) {
                ages[i] = 0;
                randomize(j, field);
            }

            const x0 = particles[j];
            const y0 = particles[j + 1];
            let dx = particles[j + 2];
            let dy = particles[j + 3];
            const x1 = x0 + dx * adj;
            const y1 = y0 + dy * adj;
            const m = particles[j + 4];

            if (m !== m || !field.isDefined(x1, y1)) {
                ages[i] = maxAge;  // particle has escaped the game grid
            } else {
                particles[j] = x1;
                particles[j + 1] = y1;

                // width of wave
                const mag = Math.sqrt(dx*dx + dy*dy) / 2.5;  // CONSIDER: would be nice to retain unscaled m...
                dx /= mag;
                dy /= mag;

                // Path from (x,y) to (xt,yt) is visible, so add this particle to the appropriate draw bucket.
                const si = colorStyles.indexFor(m * easeFactor[ages[i]]);
                const sj = 4 * sizes[si]++;
                const batch = batches[si];
                batch[sj  ] = x0 - dy;
                batch[sj+1] = y0 + dx;
                batch[sj+2] = x0 + dy;
                batch[sj+3] = y0 - dx;
            }
        }
    }

    function evolveParticles() {
        for (let s = 0; s < sizes.length; s++) {
            sizes[s] = 0;
        }
        for (let i = 0, j = 0; i < particleCount; i += 1, j += 5) {
            if (++ages[i] >= maxAge) {
                ages[i] = 0;
                randomize(j, field);
            }

            const x0 = particles[j];         // x
            const y0 = particles[j+1];       // y
            const x1 = x0 + particles[j+2];  // dx
            const y1 = y0 + particles[j+3];  // dy
            const m = particles[j+4];        // m

            if (x1 === x1) {
                field.move(x1, y1, particles, j);
                const dx = particles[j+2];
                if (dx === dx) {
                    // Path from (x0,y0) to (x1,y1) is visible, so add this particle to the appropriate draw bucket.
                    const si = colorStyles.indexFor(m);
                    const sj = 4 * sizes[si]++;
                    const batch = batches[si];
                    batch[sj  ] = x0;
                    batch[sj+1] = y0;
                    batch[sj+2] = x1;
                    batch[sj+3] = y1;
                } else {
                    ages[i] = maxAge;  // particle has escaped the game grid
                }
            } else {
                ages[i] = maxAge;  // particle has escaped the game grid
            }
        }
    }

    function draw() {
        // Fade existing trails.
        g.globalCompositeOperation = "destination-in";
        g.fillRect(xMin, yMin, width, height);
        g.globalCompositeOperation = "source-over";

        // Draw new trails.
        for (let i = 0; i < batches.length; i++) {
            const batch = batches[i];
            const size = 4 * sizes[i];
            if (size > 0) {
                g.beginPath();
                g.strokeStyle = colorStyles[i];
                for (let j = 0; j < size; j += 4) {
                    g.moveTo(batch[j  ], batch[j+1]);
                    g.lineTo(batch[j+2], batch[j+3]);
                }
                g.stroke();
            }
        }
    }

    function frame() {
        if (cancel.requested) {
            return false;
        }
        evolve();
        draw();
        setTimeout(frame, FRAME_RATE);
        return true;
    }
    frame();
    return {frame};
}

export function defineAnimator(
    animatorAgent,
    viewboxAgent,
    globeAgent,
    rendererAgent,
    primaryLayerAgent,
    fieldAgent,
    animation_enabled,
) {

    function startAnimation() {
        animatorAgent.submit(function() {
            return animateParticles(viewboxAgent, globeAgent, primaryLayerAgent, fieldAgent, this.cancel);
        });
    }

    function stopAnimation(alsoClearCanvas) {
        if (animatorAgent.value()) {
            animatorAgent.submit(false);
        }
        if (alsoClearCanvas) {
            twod.clearCanvas(d3.select("#animation").node());
        }
    }

    animation_enabled.on($`change`, enabled => {
        if (enabled) {
            startAnimation();  // startInterpolation();
        } else {
            stopAnimation(true);
        }
    });

    fieldAgent.on($`submit`, () => stopAnimation(false));
    fieldAgent.on($`update`, startAnimation);
    rendererAgent.on($`start`, () => stopAnimation(true));
}
